home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / backend.py < prev    next >
Text File  |  2009-09-23  |  22KB  |  608 lines

  1. #
  2. # backend.py: interface for backends
  3. #
  4. # Copyright 2008-2009 Canonical Ltd.
  5. #
  6. #    This program is free software: you can redistribute it and/or modify
  7. #    it under the terms of the GNU General Public License version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import os
  20. import re
  21. import stat
  22. from stat import *
  23. import sys
  24. import ufw.util
  25. from ufw.util import warn, debug
  26. from ufw.common import UFWError, config_dir, iptables_dir, UFWRule
  27. import ufw.applications
  28.  
  29. class UFWBackend:
  30.     '''Interface for backends'''
  31.     def __init__(self, name, d, extra_files={}):
  32.         self.defaults = {}
  33.         self.name = name
  34.         self.dryrun = d
  35.         self.rules = []
  36.         self.rules6 = []
  37.  
  38.         self.files = {'defaults': os.path.join(config_dir, 'default/ufw'),
  39.                       'conf': os.path.join(config_dir, 'ufw/ufw.conf'),
  40.                       'apps': os.path.join(config_dir, 'ufw/applications.d') }
  41.         self.files.update(extra_files)
  42.  
  43.         self.loglevels = {'off':       0,
  44.                           'low':     100,
  45.                           'medium':  200,
  46.                           'high':    300,
  47.                           'full':    400 }
  48.  
  49.         self.do_checks = True
  50.         try:
  51.             self._do_checks()
  52.             self._get_defaults()
  53.             self._read_rules()
  54.         except Exception:
  55.             raise
  56.  
  57.         self.profiles = ufw.applications.get_profiles(self.files['apps'])
  58.  
  59.         self.iptables = os.path.join(iptables_dir, "iptables")
  60.         self.iptables_restore = os.path.join(iptables_dir, "iptables-restore")
  61.         self.ip6tables = os.path.join(iptables_dir, "ip6tables")
  62.         self.ip6tables_restore = os.path.join(iptables_dir, \
  63.                                               "ip6tables-restore")
  64.  
  65.         self.iptables_version = ufw.util.get_iptables_version(self.iptables)
  66.  
  67.     def _is_enabled(self):
  68.         if self.defaults.has_key('enabled') and \
  69.            self.defaults['enabled'] == 'yes':
  70.             return True
  71.         return False
  72.  
  73.     def use_ipv6(self):
  74.         if self.defaults.has_key('ipv6') and \
  75.            self.defaults['ipv6'] == 'yes' and \
  76.            os.path.exists("/proc/sys/net/ipv6"):
  77.             return True
  78.         return False
  79.  
  80.     def _do_checks(self):
  81.         '''Perform basic security checks:
  82.         is setuid or setgid (for non-Linux systems)
  83.         checks that script is owned by root
  84.         checks that every component in absolute path are owned by root
  85.         warn if script is group writable
  86.         warn if part of script path is group writable
  87.  
  88.         Doing this at the beginning causes a race condition with later
  89.         operations that don't do these checks.  However, if the user running
  90.         this script is root, then need to be root to exploit the race
  91.         condition (and you are hosed anyway...)
  92.         '''
  93.  
  94.         if not self.do_checks:
  95.             err_msg = _("Checks disabled")
  96.             warn(err_msg)
  97.             return True
  98.  
  99.         # Not needed on Linux, but who knows the places we will go...
  100.         if os.getuid() != os.geteuid():
  101.             err_msg = _("ERROR: this script should not be SUID")
  102.             raise UFWError(err_msg)
  103.         if os.getgid() != os.getegid():
  104.             err_msg = _("ERROR: this script should not be SGID")
  105.             raise UFWError(err_msg)
  106.         uid = os.getuid()
  107.  
  108.         if uid != 0:
  109.             err_msg = _("You need to be root to run this script")
  110.             raise UFWError(err_msg)
  111.  
  112.         # Use these so we only warn once
  113.         warned_world_write = {}
  114.         warned_group_write = {}
  115.         warned_owner = {}
  116.  
  117.         profiles = []
  118.         if not os.path.isdir(self.files['apps']):
  119.             warn_msg = _("'%s' does not exist") % (self.files['apps'])
  120.             warn(warn_msg)
  121.         else:
  122.             pat = re.compile(r'^\.')
  123.             for profile in os.listdir(self.files['apps']):
  124.                 if not pat.search(profile):
  125.                     profiles.append(os.path.join(self.files['apps'], profile))
  126.  
  127.         for path in self.files.values() + [ os.path.abspath(sys.argv[0]) ] + \
  128.                 profiles:
  129.             while True:
  130.                 debug("Checking " + path)
  131.                 if path == self.files['apps'] and \
  132.                            not os.path.isdir(self.files['apps']):
  133.                     break
  134.  
  135.                 try:
  136.                     statinfo = os.stat(path)
  137.                     mode = statinfo[ST_MODE]
  138.                 except OSError, e:
  139.                     err_msg = _("Couldn't stat '%s'") % (path)
  140.                     raise UFWError(err_msg)
  141.                 except Exception:
  142.                     raise
  143.  
  144.                 if statinfo.st_uid != 0 and not warned_owner.has_key(path):
  145.                     warn_msg = _("uid is %(uid)s but '%(path)s' is owned by " \
  146.                                  "%(st_uid)s") % ({'uid': str(uid), \
  147.                                                'path': path, \
  148.                                                'st_uid': str(statinfo.st_uid)})
  149.                     warn(warn_msg)
  150.                     warned_owner[path] = True
  151.                 if mode & S_IWOTH and not warned_world_write.has_key(path):
  152.                     warn_msg = _("%s is world writable!") % (path)
  153.                     warn(warn_msg)
  154.                     warned_world_write[path] = True
  155.                 if mode & S_IWGRP and not warned_group_write.has_key(path):
  156.                     warn_msg = _("%s is group writable!") % (path)
  157.                     warn(warn_msg)
  158.                     warned_group_write[path] = True
  159.  
  160.                 if path == "/":
  161.                     break
  162.  
  163.                 path = os.path.dirname(path)
  164.                 if not path:
  165.                     raise
  166.  
  167.         for f in self.files:
  168.             if f != 'apps' and not os.path.isfile(self.files[f]):
  169.                 err_msg = _("'%(f)s' file '%(name)s' does not exist") % \
  170.                            ({'f': f, 'name': self.files[f]})
  171.                 raise UFWError(err_msg)
  172.  
  173.     def _get_defaults(self):
  174.         '''Get all settings from defaults file'''
  175.         self.defaults = {}
  176.         for f in [self.files['defaults'], self.files['conf']]:
  177.             try:
  178.                 orig = ufw.util.open_file_read(f)
  179.             except Exception:
  180.                 err_msg = _("Couldn't open '%s' for reading") % (f)
  181.                 raise UFWError(err_msg)
  182.             pat = re.compile(r'^\w+="?\w+"?')
  183.             for line in orig:
  184.                 if pat.search(line):
  185.                     tmp = re.split(r'=', line.strip())
  186.                     self.defaults[tmp[0].lower()] = tmp[1].lower().strip('"\'')
  187.  
  188.             orig.close()
  189.  
  190.         # do some default policy sanity checking
  191.         policies = ['accept', 'accept_no_track', 'drop', 'reject']
  192.         for c in [ 'input', 'output', 'forward' ]:
  193.             if not self.defaults.has_key('default_%s_policy' % (c)):
  194.                 err_msg = _("Missing policy for '%s'" % (c))
  195.                 raise UFWError(err_msg)
  196.             p = self.defaults['default_%s_policy' % (c)]
  197.             if p not in policies or \
  198.                (p == 'accept_no_track' and c == 'forward'):
  199.                 err_msg = _("Invalid policy '%(policy)s' for '%(chain)s'" % \
  200.                             ({'policy': p, 'chain': c}))
  201.                 raise UFWError(err_msg)
  202.  
  203.     def set_default(self, f, opt, value):
  204.         '''Sets option in defaults file'''
  205.         if not re.match(r'^[\w_]+$', opt):
  206.             err_msg = _("Invalid option")
  207.             raise UFWError(err_msg)
  208.  
  209.         # Perform this here so we can present a nice error to the user rather
  210.         # than a traceback
  211.         if not os.access(f, os.W_OK):
  212.             err_msg = _("'%s' is not writable" % (f))
  213.             raise UFWError(err_msg)
  214.  
  215.         try:
  216.             fns = ufw.util.open_files(f)
  217.         except Exception:
  218.             raise
  219.         fd = fns['tmp']
  220.  
  221.         found = False
  222.         pat = re.compile(r'^' + opt + '=')
  223.         for line in fns['orig']:
  224.             if pat.search(line):
  225.                 ufw.util.write_to_file(fd, opt + "=" + value + "\n")
  226.                 found = True
  227.             else:
  228.                 ufw.util.write_to_file(fd, line)
  229.  
  230.         # Add the entry if not found
  231.         if not found:
  232.             ufw.util.write_to_file(fd, opt + "=" + value + "\n")
  233.  
  234.         try:
  235.             ufw.util.close_files(fns)
  236.         except Exception:
  237.             raise
  238.  
  239.         # Now that the files are written out, update value in memory
  240.         self.defaults[opt.lower()] = value.lower().strip('"\'')
  241.  
  242.     def set_default_application_policy(self, policy):
  243.         '''Sets default application policy of firewall'''
  244.         if not self.dryrun:
  245.             if policy == "allow":
  246.                 try:
  247.                     self.set_default(self.files['defaults'], \
  248.                                             "DEFAULT_APPLICATION_POLICY", \
  249.                                             "\"ACCEPT\"")
  250.                 except Exception:
  251.                     raise
  252.             elif policy == "deny":
  253.                 try:
  254.                     self.set_default(self.files['defaults'], \
  255.                                             "DEFAULT_APPLICATION_POLICY", \
  256.                                             "\"DROP\"")
  257.                 except Exception:
  258.                     raise
  259.             elif policy == "reject":
  260.                 try:
  261.                     self.set_default(self.files['defaults'], \
  262.                                             "DEFAULT_APPLICATION_POLICY", \
  263.                                             "\"REJECT\"")
  264.                 except Exception:
  265.                     raise
  266.             elif policy == "skip":
  267.                 try:
  268.                     self.set_default(self.files['defaults'], \
  269.                                             "DEFAULT_APPLICATION_POLICY", \
  270.                                             "\"SKIP\"")
  271.                 except Exception:
  272.                     raise
  273.             else:
  274.                 err_msg = _("Unsupported policy '%s'") % (policy)
  275.                 raise UFWError(err_msg)
  276.  
  277.         rstr = _("Default application policy changed to '%s'") % (policy)
  278.  
  279.         return rstr
  280.  
  281.     def get_app_rules_from_template(self, template):
  282.         '''Return a list of UFWRules based on the template rule'''
  283.         rules = []
  284.         profile_names = self.profiles.keys()
  285.  
  286.         if template.dport in profile_names and template.sport in profile_names:
  287.             dports = ufw.applications.get_ports(self.profiles[template.dport])
  288.             sports = ufw.applications.get_ports(self.profiles[template.sport])
  289.             for i in dports:
  290.                 tmp = template.dup_rule()
  291.                 tmp.dapp = ""
  292.                 tmp.set_port("any", "src")
  293.                 try:
  294.                     (port, proto) = ufw.util.parse_port_proto(i)
  295.                     tmp.set_protocol(proto)
  296.                     tmp.set_port(port, "dst")
  297.                 except Exception:
  298.                     raise
  299.  
  300.                 tmp.dapp = template.dapp
  301.  
  302.                 if template.dport == template.sport:
  303.                     # Just use the same ports as dst for src when they are the
  304.                     # same to avoid duplicate rules
  305.                     tmp.sapp = ""
  306.                     try:
  307.                         (port, proto) = ufw.util.parse_port_proto(i)
  308.                         tmp.set_protocol(proto)
  309.                         tmp.set_port(port, "src")
  310.                     except Exception:
  311.                         raise
  312.  
  313.                     tmp.sapp = template.sapp
  314.                     rules.append(tmp)
  315.                 else:
  316.                     for j in sports:
  317.                         rule = tmp.dup_rule()
  318.                         rule.sapp = ""
  319.                         try:
  320.                             (port, proto) = ufw.util.parse_port_proto(j)
  321.                             rule.set_protocol(proto)
  322.                             rule.set_port(port, "src")
  323.                         except Exception:
  324.                             raise
  325.  
  326.                         if rule.protocol == "any":
  327.                             rule.set_protocol(tmp.protocol)
  328.  
  329.                         rule.sapp = template.sapp
  330.                         rules.append(rule)
  331.         elif template.sport in profile_names:
  332.             for p in ufw.applications.get_ports(self.profiles[template.sport]):
  333.                 rule = template.dup_rule()
  334.                 rule.sapp = ""
  335.                 try:
  336.                     (port, proto) = ufw.util.parse_port_proto(p)
  337.                     rule.set_protocol(proto)
  338.                     rule.set_port(port, "src")
  339.                 except Exception:
  340.                     raise
  341.  
  342.                 rule.sapp = template.sapp
  343.                 rules.append(rule)
  344.         elif template.dport in profile_names:
  345.             for p in ufw.applications.get_ports(self.profiles[template.dport]):
  346.                 rule = template.dup_rule()
  347.                 rule.dapp = ""
  348.                 try:
  349.                     (port, proto) = ufw.util.parse_port_proto(p)
  350.                     rule.set_protocol(proto)
  351.                     rule.set_port(port, "dst")
  352.                 except Exception:
  353.                     raise
  354.  
  355.                 rule.dapp = template.dapp
  356.                 rules.append(rule)
  357.  
  358.         if len(rules) < 1:
  359.             err_msg = _("No rules found for application profile")
  360.             raise UFWError(err_msg)
  361.  
  362.         return rules
  363.  
  364.     def update_app_rule(self, profile):
  365.         '''Update rule for profile in place. Returns result string and bool
  366.            on whether or not the profile is used in the current ruleset.
  367.         '''
  368.         updated_rules = []
  369.         updated_rules6 = []
  370.         last_tuple = ""
  371.         rstr = ""
  372.         updated_profile = False
  373.  
  374.         # Remember, self.rules is from user[6].rules, and not the running
  375.         # firewall.
  376.         for r in self.rules + self.rules6:
  377.             if r.dapp == profile or r.sapp == profile:
  378.                 # We assume that the rules are in app rule order. Specifically,
  379.                 # if app rule has multiple rules, they are one after the other.
  380.                 # If the rule ordering changes, the below will have to change.
  381.                 tuple = r.get_app_tuple()
  382.                 if tuple == last_tuple:
  383.                     # Skip the rule if seen this tuple already (ie, it is part
  384.                     # of a known tuple).
  385.                     continue
  386.                 else:
  387.                     # Have a new tuple, so find and insert new app rules here
  388.                     template = r.dup_rule()
  389.                     template.set_protocol("any")
  390.                     if template.dapp != "":
  391.                         template.set_port(template.dapp, "dst")
  392.                     if template.sapp != "":
  393.                         template.set_port(template.sapp, "src")
  394.                     try:
  395.                         new_app_rules = self.get_app_rules_from_template(\
  396.                                           template)
  397.                     except Exception:
  398.                         raise
  399.  
  400.                     for new_r in new_app_rules:
  401.                         new_r.normalize()
  402.                         if new_r.v6:
  403.                             updated_rules6.append(new_r)
  404.                         else:
  405.                             updated_rules.append(new_r)
  406.  
  407.                     last_tuple = tuple
  408.                     updated_profile = True
  409.             else:
  410.                 if r.v6:
  411.                     updated_rules6.append(r)
  412.                 else:
  413.                     updated_rules.append(r)
  414.  
  415.         if updated_profile:
  416.             self.rules = updated_rules
  417.             self.rules6 = updated_rules6
  418.             rstr += _("Rules updated for profile '%s'") % (profile)
  419.  
  420.             try:
  421.                 self._write_rules(False) # ipv4
  422.                 self._write_rules(True) # ipv6
  423.             except Exception:
  424.                 err_msg = _("Couldn't update application rules")
  425.                 raise UFWError(err_msg)
  426.  
  427.         return (rstr, updated_profile)
  428.  
  429.     def find_application_name(self, str):
  430.         '''Find the application profile name for str'''
  431.         if self.profiles.has_key(str):
  432.             return str
  433.  
  434.         match = ""
  435.         matches = 0
  436.         for n in self.profiles.keys():
  437.             if n.lower() == str.lower():
  438.                 match = n
  439.                 matches += 1
  440.  
  441.         debug_msg = "'%d' matches for '%s'" % (matches, str)
  442.         debug(debug_msg)
  443.         if matches == 1:
  444.             return match
  445.         elif matches > 1:
  446.             err_msg = _("Found multiple matches for '%s'. Please use exact profile name") % (str)
  447.         err_msg = _("Could not find a profile matching '%s'") % (str)
  448.         raise UFWError(err_msg)
  449.  
  450.     def find_other_position(self, position, v6):
  451.     '''Return the absolute position in the other list of the rule with the
  452.        user position of the given list. For example, find_other_position(4,
  453.        True) will return the absolute position of the rule in the ipv4 list
  454.            matching the user specified '4' rule in the ipv6 list.
  455.         '''
  456.         # Invalid search (v6 rule with too low position)
  457.         if v6 and position > len(self.rules6):
  458.             raise ValueError()
  459.  
  460.         # Invalid search (v4 rule with too high position)
  461.         if not v6 and position > len(self.rules):
  462.             raise ValueError()
  463.  
  464.         if position < 1:
  465.             raise ValueError()
  466.  
  467.         rules = []
  468.         if v6:
  469.             rules = self.rules6
  470.         else:
  471.             rules = self.rules
  472.  
  473.         # self.rules[6] is a list of tuples. Some application rules have
  474.         # multiple tuples but the user specifies by ufw rule, not application
  475.         # tuple, so we need to find how many tuples there are leading up to
  476.         # the specified position, which we can then use as an offset for
  477.         # getting the proper match_rule.
  478.         app_rules = {}
  479.         tuple_offset = 0
  480.         for i, r in enumerate(rules):
  481.             if i >= position:
  482.                 break
  483.             tuple = ""
  484.             if r.dapp != "" or r.sapp != "":
  485.                 tuple = r.get_app_tuple()
  486.  
  487.                 if app_rules.has_key(tuple):
  488.                     tuple_offset += 1
  489.                 else:
  490.                     app_rules[tuple] = True
  491.  
  492.         rules = []
  493.         if v6:
  494.             rules = self.rules
  495.             match_rule = self.rules6[position - 1 + tuple_offset].dup_rule()
  496.             match_rule.set_v6(False)
  497.         else:
  498.             rules = self.rules6
  499.             match_rule = self.rules[position - 1 + tuple_offset].dup_rule()
  500.             match_rule.set_v6(True)
  501.  
  502.         count = 1
  503.         for r in rules:
  504.             if UFWRule.match(r, match_rule) == 0:
  505.                 return count
  506.             count += 1
  507.  
  508.         return 0
  509.  
  510.     def get_loglevel(self):
  511.         '''Gets current log level of firewall'''
  512.         level = 0
  513.         rstr = _("Logging: ")
  514.         if not self.defaults.has_key('loglevel') or \
  515.            self.defaults['loglevel'] not in self.loglevels.keys():
  516.             level = -1
  517.             rstr += _("unknown")
  518.         else:
  519.             level = self.loglevels[self.defaults['loglevel']]
  520.             if level == 0:
  521.                 rstr += "off"
  522.             else:
  523.                 rstr += "on (%s)" % (self.defaults['loglevel'])
  524.         return (level, rstr)
  525.  
  526.     def set_loglevel(self, level):
  527.         '''Sets log level of firewall'''
  528.         if level not in self.loglevels.keys() + ['on']:
  529.             err_msg = _("Invalid log level '%s'") % (level)
  530.             raise UFWError(err_msg)
  531.  
  532.         new_level = level
  533.         if level == "on":
  534.            if not self.defaults.has_key('loglevel') or \
  535.               self.defaults['loglevel'] == "off":
  536.                new_level = "low"
  537.            else:
  538.                new_level = self.defaults['loglevel']
  539.  
  540.         try:
  541.             self.set_default(self.files['conf'], "LOGLEVEL", new_level)
  542.             self.update_logging(new_level)
  543.         except:
  544.             raise
  545.  
  546.         if new_level == "off":
  547.             return _("Logging disabled")
  548.         else:
  549.             return _("Logging enabled")
  550.  
  551.     def get_rules_count(self, v6):
  552.         '''Return number of ufw rules (not iptables rules)'''
  553.         rules = []
  554.         if v6:
  555.             rules = self.rules6
  556.         else:
  557.             rules = self.rules
  558.  
  559.         count = 0
  560.         app_rules = {}
  561.         for r in rules:
  562.             tuple = ""
  563.             if r.dapp != "" or r.sapp != "":
  564.                 tuple = r.get_app_tuple()
  565.  
  566.                 if app_rules.has_key(tuple):
  567.                     debug("Skipping found tuple '%s'" % (tuple))
  568.                     continue
  569.                 else:
  570.                     app_rules[tuple] = True
  571.             count += 1
  572.  
  573.         return count
  574.  
  575.     # API overrides
  576.     def get_default_policy(self):
  577.         raise UFWError("UFWBackend.get_default_policy: need to override")
  578.  
  579.     def set_default_policy(self, policy, direction):
  580.         raise UFWError("UFWBackend.set_default_policy: need to override")
  581.  
  582.     def get_running_raw(self):
  583.         raise UFWError("UFWBackend.get_running_raw: need to override")
  584.  
  585.     def get_status(self, verbose, show_count):
  586.         raise UFWError("UFWBackend.get_status: need to override")
  587.  
  588.     def get_status_as_list(self):
  589.         raise UFWError("UFWBackend.get_status_as_list: need to override")
  590.  
  591.     def set_rule(self, rule, allow_reload):
  592.         raise UFWError("UFWBackend.set_rule: need to override")
  593.  
  594.     def start_firewall(self):
  595.         raise UFWError("UFWBackend.start_firewall: need to override")
  596.  
  597.     def stop_firewall(self):
  598.         raise UFWError("UFWBackend.stop_firewall: need to override")
  599.  
  600.     def get_app_rules_from_system(self, template, v6):
  601.         raise UFWError("UFWBackend.get_app_rules_from_system: need to " + \
  602.                        "override")
  603.  
  604.     def update_logging(self, level):
  605.         raise UFWError("UFWBackend.update_logging: need to override")
  606.  
  607.  
  608.